using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using SqlSugar;
using TianShu.LicenseAuthorizer.Abstracts.Service;
using Volo.Abp.Clients;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.SqlSugarCore.Configs;
using Volo.Abp.SqlSugarCore.DependencyInjection;
using Volo.Abp.SqlSugarCore.SqlSugarFactory;
using Volo.Abp.SqlSugarCore.SqlSugarFactory.uow;
using Volo.Abp.Threading;
using Volo.Abp.Uow;

namespace Volo.Abp.SqlSugarCore.Uow
{
    public class UnitOfWorkSugarClientProvider : IUowSqlSugarFactory, ITransientDependency
    {
        public ILogger _logger { get; private set; }

        AbpDbConnectionOptions _dbConfig;
        SqlSugarClientConfigSetting _sugarClientConfigSetting;
        SqlSugarSerializeService _sqlSugarSerializeService;
        SugarAutoGeneratorTableOptions _sugarAutoGeneratorTableOptions;

        private readonly IUnitOfWorkManager _unitOfWorkManager;
        private readonly IConnectionStringResolver _connectionStringResolver;
        private readonly ICancellationTokenProvider _cancellationTokenProvider;
        private readonly ICurrentTenant _currentTenant;

        public UnitOfWorkSugarClientProvider(
            IUnitOfWorkManager unitOfWorkManager,
            IConnectionStringResolver connectionStringResolver,
            ICancellationTokenProvider cancellationTokenProvider,
            ICurrentTenant currentTenant,
            IOptions<AbpDbConnectionOptions> connectionOptions,
            IOptions<SqlSugarClientConfigSetting> sugarConfigOptions,
            IOptions<SugarAutoGeneratorTableOptions> autoGeneratorTableOptions,
            SqlSugarSerializeService sqlSugarSerializeService,
            ILoggerFactory loggerFactory
            , ICurrentClient currentClient
            , ILiceseLoaderService liceseLoaderService)
        {
            _unitOfWorkManager = unitOfWorkManager;
            _connectionStringResolver = connectionStringResolver;
            _cancellationTokenProvider = cancellationTokenProvider;
            _currentTenant = currentTenant;
            _dbConfig = connectionOptions.Value;
            _sugarClientConfigSetting = sugarConfigOptions.Value;
            _sugarAutoGeneratorTableOptions = autoGeneratorTableOptions.Value;
            _sqlSugarSerializeService = sqlSugarSerializeService;
            _logger = loggerFactory.CreateLogger<DefaultSqlSugarClientFactory>();


            liceseLoaderService.CheckLicenseLoaderServiceImpl();
            if (currentClient.IsAuthenticated)
            {
                liceseLoaderService.CheckLicenseAuthorizer(currentTenant.Id, currentClient.Id?.ToString());
            }
        }


        public async Task<ISqlSugarClient> GetClient()
        {
            var unitOfWork = _unitOfWorkManager.Current;
            if (unitOfWork == null)
            {
                throw new ApplicationException("A DbContext can only be created inside a unit of work!");
            }

            var connectionStringName = "Default";
            var connectionString = await ResolveConnectionStringAsync(connectionStringName);

            var dbContextKey = $"{this.GetType().FullName}_{connectionString}";

            var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);

            if (databaseApi == null)
            {
                databaseApi = new SqlSugarCoreDatabaseApi(
                    await CreateDbContextAsync(unitOfWork, connectionStringName, connectionString)
                );

                unitOfWork.AddDatabaseApi(dbContextKey, databaseApi);
            }

            return ((SqlSugarCoreDatabaseApi)databaseApi).DbContext;
        }

        private async Task<ISqlSugarClient> CreateDbContextAsync(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
        {
            var creationContext = new DbContextCreationContext(connectionStringName, connectionString);
            using (DbContextCreationContext.Use(creationContext))
            {
                var dbContext = await CreateDbContextAsync(unitOfWork);

                //if (dbContext is IAbpEfCoreDbContext abpEfCoreDbContext)
                //{
                //    abpEfCoreDbContext.Initialize(
                //        new AbpEfCoreDbContextInitializationContext(
                //            unitOfWork
                //        )
                //    );
                //}

                return dbContext;
            }
        }


        private async Task<ISqlSugarClient> CreateDbContextAsync(IUnitOfWork unitOfWork)
        {
            return unitOfWork.Options.IsTransactional
                ? await CreateDbContextWithTransactionAsync(unitOfWork)
                : await NewSugarClient();
        }
        private async Task<ISqlSugarClient> CreateDbContextWithTransactionAsync(IUnitOfWork unitOfWork)
        {
            var transactionApiKey = $"SqlSugarCore_{DbContextCreationContext.Current.ConnectionString}";
            var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as SqlSugarCoreTransactionApi;

            if (activeTransaction == null)
            {
                var dbContext = await NewSugarClient();

                if (unitOfWork.Options.IsolationLevel.HasValue)
                {
                    dbContext.Ado.BeginTran(unitOfWork.Options.IsolationLevel.Value);
                }
                else
                {
                    dbContext.Ado.BeginTran();
                }

                unitOfWork.AddTransactionApi(
                    transactionApiKey,
                    new SqlSugarCoreTransactionApi(
                        dbContext.Ado.Transaction,
                        dbContext,
                        _cancellationTokenProvider
                    )
                );

                return dbContext;
            }
            else
            {
                DbContextCreationContext.Current.ExistingConnection = activeTransaction.DbContextTransaction.Connection;

                var dbContext = await NewSugarClient();

                if (dbContext.Ado.Connection == DbContextCreationContext.Current.ExistingConnection)
                {
                    dbContext.Ado.Transaction = activeTransaction.DbContextTransaction;
                }
                else
                {
                    if (unitOfWork.Options.IsolationLevel.HasValue)
                    {
                        dbContext.Ado.BeginTran(unitOfWork.Options.IsolationLevel.Value);
                    }
                    else
                    {
                        dbContext.Ado.BeginTran();
                    }
                }

                activeTransaction.AttendedDbContexts.Add(dbContext);

                return dbContext;
            }
        }

        private async Task<DbContextCreationContext> GetCreationContext()
        {
            var context = DbContextCreationContext.Current;
            if (context != null)
            {
                return context;
            }

            var connectionStringName = "Default";
            var connectionString = await ResolveConnectionStringAsync(connectionStringName);

            return new DbContextCreationContext(
                connectionStringName,
                connectionString
            );
        }

        private async Task<string> ResolveConnectionStringAsync(string connectionStringName)
        {
            // Multi-tenancy unaware contexts should always use the host connection string
            var EnableMultiTenancyKey = "EnableMultiTenancy";
            if (_dbConfig.ConnectionStrings.ContainsKey(EnableMultiTenancyKey) == false || _dbConfig.ConnectionStrings[EnableMultiTenancyKey] != "true")
            {
                using (_currentTenant.Change(null))
                {
                    return await _connectionStringResolver.ResolveAsync(connectionStringName);
                }
            }

            return await _connectionStringResolver.ResolveAsync(connectionStringName);
        }


        protected virtual CancellationToken GetCancellationToken(CancellationToken preferredValue = default)
        {
            return _cancellationTokenProvider.FallbackToProvider(preferredValue);
        }


        public async Task<ISqlSugarClient> NewSugarClient()
        {
            var creationContext = await GetCreationContext();

            ISqlSugarClient context = null;
            //×Ô¶¨ÒåÀ©Õ¹·½·¨
            var expMethods = new List<SqlFuncExternal>();
            expMethods.Add(new SqlFuncExternal()
            {
                UniqueMethodName = "GroupConcat",
                MethodValue = (expInfo, dType, expContext) =>
                {
                    if (dType == SqlSugar.DbType.MySql)
                        return string.Format("GROUP_CONCAT({0})", expInfo.Args[0].MemberName);
                    else
                        throw new ApplicationException("Î´ÊµÏÖGroupConcat·½·¨");
                }
            });


            SqlSugar.DbType dbType = GetDbType(_dbConfig.ConnectionStrings.ContainsKey("Provide") ? _dbConfig.ConnectionStrings["Provide"] : "");

            context = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = creationContext.ConnectionString,
                DbType = dbType,
                InitKeyType = InitKeyType.Attribute,//´ÓÌØÐÔ¶ÁÈ¡Ö÷¼üºÍ×ÔÔöÁÐÐÅÏ¢
                IsAutoCloseConnection = true,//¿ªÆô×Ô¶¯ÊÍ·ÅÄ£Ê½
                ConfigureExternalServices = new ConfigureExternalServices()
                {
                    EntityService = (t, column) =>
                    {
                        if (column.PropertyName.ToLower() == "id") //ÊÇidµÄÉèÎªÖ÷¼ü
                        {
                            column.IsPrimarykey = true;
                            if (column.PropertyInfo.PropertyType == typeof(int)) //ÊÇid²¢ÇÒÊÇintµÄÊÇ×ÔÔö
                            {
                                column.IsIdentity = true;
                            }
                        }
                        if (column.PropertyName.ToLower() == "creationname") //´´½¨ÈËÃû³Æ
                        {
                            column.Length = 64;
                            column.IsNullable = false;
                            column.DefaultValue = "";
                        }
                        var columnName = column.PropertyName.ToLower();
                        if (columnName == "deleterid" || columnName == "deletiontime" || columnName == "lastmodificationtime" || columnName == "lastmodifierid") //
                        {
                            column.IsNullable = true;
                        }
                        if (column.PropertyName.ToLower() == "CreatorId") //´´½¨ÈËÃû³Æ
                        {
                            column.Length = 36;
                            column.IsNullable = false;
                            column.DefaultValue = Guid.Empty.ToString();
                        }
                        if (column.PropertyName.ToLower() == "CreationTime") //´´½¨ÈËÃû³Æ
                        {
                            column.IsNullable = false;
                            column.DefaultValue = DateTime.Now.ToString();
                        }
                        if (column.PropertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
                        {
                            column.IsJson = true;
                            column.DefaultValue = "";
                        }
                    },
                    EntityNameService = (entype, en) =>
                    {
                        if (_sugarAutoGeneratorTableOptions.IsCreateDatabase() == false)
                        {
                            en.IsDisabledUpdateAll = true;
                        }
                    },
                    SerializeService = _sqlSugarSerializeService,

                    SqlFuncServices = expMethods//set ext method
                }
            });

            //Ö´ÐÐÖ®Ç°
            context.Aop.OnLogExecuting = (sql, pars) =>
            {
                _logger.LogDebug("sql=" + sql + @"\r\n²ÎÊýÎª£º" +
                                context.Utilities.SerializeObject((pars).ToDictionary(it => it.ParameterName,
                                    it => it.Value)));

            };
            //Ö´ÐÐÍê³É
            context.Aop.OnLogExecuted = (sql, pars) =>
            {
                _logger.LogDebug("Ö´ÐÐ»¨·ÑÊ±¼ä£º" + context.Ado.SqlExecutionTime.TotalMilliseconds + "mm");
                //
                if (context.Ado.SqlExecutionTime.TotalMilliseconds > 1000)
                {

                }
            };
            //·¢ÉúÒì³£
            context.Aop.OnError = (exp) =>
            {
                _logger.LogError("sql=" + exp.Sql + @"\r\n²ÎÊýÎª£º" +
                                context.Utilities.SerializeObject(((SugarParameter[])exp.Parametres).ToDictionary(it => it.ParameterName,
                                    it => it.Value)));

                _logger.LogError("sqlÖ´ÐÐ·¢ËÍÒì³££º", exp.InnerException);
            };


            ///Ö´ÐÐÍâÃæÅäÖÃÏî
            _sugarClientConfigSetting.ExecSugarClientConfigAction(context);


            return context;
        }

        private DbType GetDbType(string dbtype)
        {
            DbType dbType = DbType.MySql;
            switch (dbtype)
            {
                case "sqlserver":
                    dbType = SqlSugar.DbType.SqlServer;
                    break;
                case "mysql":
                    dbType = SqlSugar.DbType.MySql;
                    break;
                case "oracle":
                    dbType = SqlSugar.DbType.Oracle;
                    break;
                case "sqlite":
                    dbType = SqlSugar.DbType.Sqlite;
                    break;
                case "postgresql":
                    dbType = SqlSugar.DbType.PostgreSQL;
                    break;
                default:
                    throw new ApplicationException("²»Ö§³ÖµÄÊý¾Ý¿â");
            }

            return dbType;
        }
    }
}
